package ru.naumen.gintonic.context.refactor;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ITrackedNodePosition;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import ru.naumen.gintonic.utils.ASTParserUtils;
import ru.naumen.gintonic.utils.Preconditions;
import ru.naumen.gintonic.utils.StringUtils;
public class Refactorator {
private final ICompilationUnit icompilationUnit;
private final AST ast;
private final RefactoratorProvider provider;
private final CompilationUnit compilationUnit;
private Refactorator(ICompilationUnit icompilationUnit,
CompilationUnit compilationUnitAstNode,
AST ast) {
this.compilationUnit = compilationUnitAstNode;
Preconditions.checkNotNull(ast);
Preconditions.checkNotNull(icompilationUnit);
this.ast = ast;
this.icompilationUnit = icompilationUnit;
provider = new RefactoratorProvider(ast, icompilationUnit);
}
public static Refactorator create(ICompilationUnit icompilationUnit,
CompilationUnit compilationUnit, AST ast) {
return new Refactorator(icompilationUnit, compilationUnit, ast);
}
private ImportRewrite getImportRewrite() {
return provider.getImportRewrite();
}
private ASTRewrite getAstRewrite() {
return provider.getAstRewrite();
}
public TrackedStatement addAsLastStatementInMethod(
MethodDeclaration method, String statement) {
Statement bindingStatement = ASTParserUtils.parseStatementAst3(statement);
Block methodBody = method.getBody();
ASTRewrite astRewrite = getAstRewrite();
ITrackedNodePosition track = astRewrite.track(bindingStatement);
ListRewrite statementsListRewrite = astRewrite.getListRewrite(
methodBody,
Block.STATEMENTS_PROPERTY);
statementsListRewrite.insertLast(bindingStatement, null);
int startPosition = track.getStartPosition();
int length = track.getLength();
bindingStatement.setSourceRange(startPosition, length);
return new TrackedStatement(bindingStatement, track);
}
@SuppressWarnings("unchecked")
public TrackedMethodDeclaration addMethodDeclaration(
MethodDeclaration methodDeclaration) {
ASTRewrite astRewrite = getAstRewrite();
ITrackedNodePosition track = astRewrite.track(methodDeclaration);
List<TypeDeclaration> types = compilationUnit.types();
int nrOfTypes = types.size();
if (nrOfTypes > 0) {
TypeDeclaration typeDeclSource = types.get(0);
ListRewrite container = astRewrite.getListRewrite(
typeDeclSource,
TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
container.insertLast(methodDeclaration, null);
}
return new TrackedMethodDeclaration(methodDeclaration, track);
}
/**
* Adds the given method and returns it as {@link MethodDeclaration}.
*
* @param methodCode the java code of the method declaration.
*/
public TrackedMethodDeclaration addMethod(String methodCode) {
TypeDeclaration typeDeclarationNew = ASTParserUtils.parseTypeDeclaration(methodCode);
MethodDeclaration[] methods = typeDeclarationNew.getMethods();
MethodDeclaration methodDeclaration = methods[0];
return addMethodDeclaration(methodDeclaration);
}
/**
* Adds a new import to the rewriter's record and returns a type reference
* that can be used in the code.
*/
public String addImport(IType type) {
return addImport(type.getFullyQualifiedName());
}
/**
* See {@link ImportRewrite#addImport(ITypeBinding)}.
*/
public String addImport(ITypeBinding typeBinding) {
ImportRewrite importRewrite = getImportRewrite();
return importRewrite.addImport(typeBinding);
}
/**
* Adds a new import to the rewriter's record and returns a type reference
* that can be used in the code.
*/
public String addImport(String fullyClassifiedTypeName) {
ImportRewrite importRewrite = getImportRewrite();
return importRewrite.addImport(fullyClassifiedTypeName);
}
public void changeType(Type oldType, Type newType) {
ASTRewrite astRewrite = getAstRewrite();
astRewrite.replace(oldType, newType, null);
}
/**
* Creates a new {@link ParameterizedType} by wrapping the given
* {@link Type} inside the wrapperType. We also add the new type as import
* statement.
*
* <h5>Example:</h5>
*
* Given the following field declaration:
*
* <pre>
* public Snake<?> snake;
* </pre>
*
* You can change the <code>Snake <?></code> type to
* <code>Provider<Snake></code> by coding:
*
* <pre>
* <code>refactorator.changeTypeByWrappingIt(snakeType, "com.google.inject.Provider");</code>
* </pre>
*
*
* @param ast
* @param parameterizedType
* @param targetTypeNameFullyQualified the fully qualified type that wraps
* the
* @return the new {@link ParameterizedType}
*/
@SuppressWarnings("unchecked")
public ParameterizedType changeTypeByWrappingIt(Type sourceType,
String targetTypeNameFullyQualified) {
AST ast = sourceType.getAST();
String targetTypeNameSimple = StringUtils.qualifiedNameToSimpleName(targetTypeNameFullyQualified);
SimpleName simpleName = ast.newSimpleName(targetTypeNameSimple);
ParameterizedType targetType = ast.newParameterizedType(ast.newSimpleType(simpleName));
Type typeUnbound = (Type) ASTNode.copySubtree(ast, sourceType);
List<Type> typeArguments = targetType.typeArguments();
typeArguments.add(typeUnbound);
addImport(targetTypeNameFullyQualified);
changeType(sourceType, targetType);
return targetType;
}
public void renameVariableIdentifiers(
VariableDeclarationFragment variableDeclarationFragment,
String newName) {
ASTRewrite astRewrite = getAstRewrite();
SimpleName oldVariableName = variableDeclarationFragment.getName();
SimpleName newVariableName = ast.newSimpleName(newName);
/* Replace the old with the new name */
astRewrite.replace(oldVariableName, newVariableName, null);
}
/**
* Renames all identifiers of the given fieldDeclaration.
*
* @param fieldDeclaration the {@link FieldDeclaration}
* @param newName the newName. The name can include a $ sign which is then
* replaced by the original identifier. Use this if you want some
* part of the original name to be preserved.
*/
public void renameFieldIdentifiers(FieldDeclaration fieldDeclaration,
String newName) {
@SuppressWarnings("unchecked")
List<VariableDeclarationFragment> fragments = fieldDeclaration.fragments();
ASTRewrite astRewrite = getAstRewrite();
for (VariableDeclarationFragment variableDeclarationFragment : fragments) {
SimpleName name = variableDeclarationFragment.getName();
String oldVariableName = name.getIdentifier();
/* Substitute $ placeholder with the old variable name */
String newVariableNameAsString = newName;
if (newVariableNameAsString.contains("$")) {
newVariableNameAsString = newVariableNameAsString.replace(
"$",
oldVariableName);
}
SimpleName newVariableName = ast.newSimpleName(newVariableNameAsString);
/* Replace the old with the new name */
astRewrite.replace(name, newVariableName, null);
}
}
public void refactor(IProgressMonitor progressMonitor) {
try {
ICompilationUnit workingCopy = icompilationUnit.getWorkingCopy(progressMonitor);
String originalSource = workingCopy.getSource();
Document sourceDocument = new Document(originalSource);
MultiTextEdit multiTextEdit = new MultiTextEdit();
rewriteImports(progressMonitor, multiTextEdit);
rewriteAST(sourceDocument, multiTextEdit);
workingCopy.applyTextEdit(multiTextEdit, progressMonitor);
workingCopy.reconcile(
ICompilationUnit.NO_AST,
false,
null,
progressMonitor);
workingCopy.commitWorkingCopy(true, progressMonitor);
workingCopy.discardWorkingCopy();
} catch (Exception e) {
throw new RefactoratorException(e);
}
}
@SuppressWarnings("rawtypes")
private void rewriteAST(Document sourceDocument, MultiTextEdit multiTextEdit) {
IJavaProject javaProject = icompilationUnit.getJavaProject();
Map options = javaProject.getOptions(true);
ASTRewrite astRewrite = getAstRewrite();
TextEdit astRewriteTextEdit = astRewrite.rewriteAST(
sourceDocument,
options);
if (astRewriteTextEdit != null) {
multiTextEdit.addChild(astRewriteTextEdit);
}
}
private void rewriteImports(IProgressMonitor progressMonitor,
MultiTextEdit multiTextEdit) throws CoreException {
ImportRewrite importRewrite = getImportRewrite();
TextEdit importRewriteTextEdit = importRewrite.rewriteImports(progressMonitor);
if (importRewriteTextEdit != null) {
multiTextEdit.addChild(importRewriteTextEdit);
}
}
/**
* I don't want to have the ImportRewrite and ASTRewrite as fields of
* Refactorator as they are created on demand. So this is what this
* RefactoratorProvider is for.
*
* @author tmajunke
*/
private static class RefactoratorProvider {
private AST ast;
private ICompilationUnit compilationUnit;
private ImportRewrite importRewrite;
private ASTRewrite astRewrite;
private RefactoratorProvider(AST ast, ICompilationUnit compilationUnit) {
super();
this.ast = ast;
this.compilationUnit = compilationUnit;
}
private ImportRewrite getImportRewrite() {
if (importRewrite == null) {
try {
importRewrite = ImportRewrite.create(compilationUnit, true);
} catch (JavaModelException e) {
throw new RefactoratorException(e);
}
}
return importRewrite;
}
private ASTRewrite getAstRewrite() {
if (astRewrite == null) {
astRewrite = ASTRewrite.create(ast);
}
return astRewrite;
}
}
}